#ifndef COMMON_MATHS_VECTOR3_H
#define COMMON_MATHS_VECTOR3_H


#include <math.h>


class Vector3;
typedef const Vector3& Vector3Arg;


Vector3 Abs(Vector3Arg v);
float   Dot(Vector3Arg a, Vector3Arg b);
Vector3 Cross(Vector3Arg a, Vector3Arg b);
Vector3 Min(Vector3Arg a, Vector3Arg b);
Vector3 Max(Vector3Arg a, Vector3Arg b);
float   LengthSqr(Vector3Arg v);
float   Length(Vector3Arg v);
float   DistanceSqr(Vector3Arg a, Vector3Arg b);
float   Distance(Vector3Arg a, Vector3Arg b);
Vector3 Normalize(Vector3Arg v);
Vector3 SafeNormalize(Vector3Arg v, Vector3Arg fallback);
Vector3 Lerp(Vector3Arg a, Vector3Arg b, float t);


class Vector3
{
public:
    enum ZeroTag { kZero };

    float       x;
    float       y;
    float       z;
    
                Vector3();
                Vector3(const ZeroTag&);
                Vector3(float x, float y, float z);
    
    Vector3     operator+(Vector3Arg v) const;
    Vector3     operator-(Vector3Arg v) const;
    Vector3     operator*(Vector3Arg v) const;
    Vector3     operator/(Vector3Arg v) const;
    Vector3     operator*(float f) const;
    Vector3     operator/(float f) const;

    Vector3&    operator+=(Vector3Arg v);
    Vector3&    operator-=(Vector3Arg v);
    Vector3&    operator*=(Vector3Arg v);
    Vector3&    operator*=(float f);
    Vector3&    operator/=(float f);
    
    Vector3     operator-() const;

    float       Get(size_t idx) const;
    void        Set(size_t idx, float f);
};


inline Vector3::Vector3()
{
}

inline Vector3::Vector3(const ZeroTag&)
    : x(0.0f) , y(0.0f) , z(0.0f) { }

inline Vector3::Vector3(float x, float y, float z)
    : x(x) , y(y) , z(z) { }

inline Vector3 Vector3::operator+(Vector3Arg v) const
{
    return Vector3(x+v.x, y+v.y, z+v.z);
}

inline Vector3 Vector3::operator-(Vector3Arg v) const
{
    return Vector3(x-v.x, y-v.y, z-v.z);
}

inline Vector3 Vector3::operator*(Vector3Arg v) const
{
    return Vector3(x*v.x, y*v.y, z*v.z);
}

inline Vector3 Vector3::operator/(Vector3Arg v) const
{
    return Vector3(x/v.x, y/v.y, z/v.z);
}

inline Vector3 Vector3::operator*(float f) const
{
    return Vector3(f*x, f*y, f*z);
}

inline Vector3 Vector3::operator/(float f) const
{
    const float r = 1.0f / f;
    return Vector3(r*x, r*y, r*z);
}

inline Vector3& Vector3::operator+=(Vector3Arg v)
{
    x += v.x;
    y += v.y;
    z += v.z;
    return *this;
}

inline Vector3& Vector3::operator-=(Vector3Arg v)
{
    x -= v.x;
    y -= v.y;
    z -= v.z;
    return *this;
}

inline Vector3& Vector3::operator*=(Vector3Arg v)
{
    x *= v.x;
    y *= v.y;
    z *= v.z;
    return *this;
}

inline Vector3& Vector3::operator*=(float f)
{
    x *= f;
    y *= f;
    z *= f;
    return *this;
}

inline Vector3& Vector3::operator/=(float f)
{
    const float r = 1.0f / f;
    x *= r;
    y *= r;
    z *= r;
    return *this;
}

inline Vector3 Vector3::operator-() const
{
    return Vector3(-x, -y, -z);
}

inline float Vector3::Get(size_t idx) const
{
    return (&x)[idx];
}

inline void Vector3::Set(size_t idx, float f)
{
    (&x)[idx] = f;
}

inline Vector3 operator*(float f, Vector3Arg v)
{
    return Vector3(f*v.x, f*v.y, f*v.z);
}

inline Vector3 operator/(float f, Vector3Arg v)
{
    return Vector3(f/v.x, f/v.y, f/v.z);
}

inline Vector3 Abs(Vector3Arg v)
{
    return Vector3(abs(v.x), abs(v.y), abs(v.z));
}

inline float Dot(Vector3Arg a, Vector3Arg b)
{
    return a.x*b.x + a.y*b.y + a.z*b.z;
}

inline Vector3 Min(Vector3Arg a, Vector3Arg b)
{
    return Vector3(
       a.x<b.x ? a.x : b.x,
       a.y<b.y ? a.y : b.y,
       a.z<b.z ? a.z : b.z
    );
}

inline Vector3 Max(Vector3Arg a, Vector3Arg b)
{
    return Vector3(
        a.x>b.x ? a.x : b.x,
        a.y>b.y ? a.y : b.y,
        a.z>b.z ? a.z : b.z
    );
}

inline Vector3 Cross(Vector3Arg a, Vector3Arg b)
{
    const float x = a.y*b.z - a.z*b.y;
    const float y = a.z*b.x - a.x*b.z;
    const float z = a.x*b.y - a.y*b.x;
    return Vector3(x, y, z);
}

inline float LengthSqr(Vector3Arg v)
{
    return v.x*v.x + v.y*v.y + v.z*v.z;
}

inline float Length(Vector3Arg v)
{
    return sqrt(LengthSqr(v));
}

inline float DistanceSqr(Vector3Arg a, Vector3Arg b)
{
    return LengthSqr(b - a);
}

inline float Distance(Vector3Arg a, Vector3Arg b)
{
    return Length(b - a);
}

inline Vector3 Normalize(Vector3Arg v)
{
    return v / Length(v);
}

inline Vector3 SafeNormalize(Vector3Arg v, Vector3Arg fallback)
{
    const float len = Length(v);
    return (len>0.0f) ? v/len : fallback;
}

inline Vector3 Lerp(Vector3Arg a, Vector3Arg b, float t)
{
    return a + t*(b-a);
}


#endif
